package com.hzit.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson2.JSON;
import com.hzit.cart.api.domain.Item;
import com.hzit.search.service.ItemSearchService;
import com.hzit.search.vo.ItemVo;
import io.jsonwebtoken.lang.Collections;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 类名：
 * 作者：WF
 * 功能：
 */
@Service
public class ItemSearchServiceImpl implements ItemSearchService {
	@Autowired
	private ElasticsearchClient client;
	@Autowired
	private StringRedisTemplate redisTemplate;
	/**
	 * 1. 开始搜索
	 * @param vo  前端传入的搜索参数
	 * @return    查询到的结果
	 */
	@Override
	public Map search(ItemVo vo) throws IOException {
		//1. 定义返回的map对象
		Map resultMap = new HashMap();
		//2. 构造查询请求对象
		SearchRequest request = null;
		//3. 定义过滤查询的条件集合对象（将多个查询统一放到一个list集合中）
		List<Query> filterQueryList = new ArrayList<>();
		//4. 定义排序的集合
		List<SortOptions> sortList = new ArrayList<>();
		//6. 获取分页参数
		int page = vo.getPage();                // 当前页
		int pageSize = vo.getPageSize();        // 每页大小
		//7. 有搜索关键字时查询
		if (StringUtils.isNotBlank(vo.getKeywords())) {
			//7.1 过滤查询一： 定义多字段匹配查询
			//7.1.1 vo.getKeywords(): 代表要查询的数据
			Query multiMQ = MultiMatchQuery.of(mmq -> mmq.fields("title", "brand", "category").query(vo.getKeywords()))._toQuery();
			//7.1.2 定义多字段匹配查询对象并放到list集合中
			filterQueryList.add(multiMQ);
			//7.2 过滤查询二： 添加分类查询
			if(StringUtils.isNotBlank(vo.getCategory())){
				//7.2.1 得到分类查询对象 （点击某个分类后进行分类查询）
				Query categoryQuery = TermQuery.of(cq -> cq.field("category").value(vo.getCategory()))._toQuery();
				//7.2.2 将分类查询对象放到查询集合中
				filterQueryList.add(categoryQuery);
			}
			//7.3 过滤查询三： 添加品牌查询
			if(StringUtils.isNotBlank(vo.getBrand())){
				//7.3.1 得到品牌查询对象
				Query brandQuery = TermQuery.of(tq -> tq.field("brand").value(vo.getBrand()))._toQuery();
				//7.3.2 将品牌查询对象放到查询列表中
				filterQueryList.add(brandQuery);
			}
			//7.4 过滤查询四： 添加规格查询
			if(vo.getSpec() != null && vo.getSpec().size() > 0){
				//7.4.1 遍历集合进行拆分
				for (String s : vo.getSpec()) {
					String[] split = s.split(":");      // 第一项：规格名称 第二项：规格值
					//7.4.2 得到一个规格查询对象
					Query specQuery = TermQuery.of(sq -> sq.field("specMap." + split[0] + ".keyword").value(split[1]))._toQuery();
					//7.4.3 将规格查询对象添加到查询列表中
					filterQueryList.add(specQuery);
				}
			}
			//7.5 过滤查询五：添加价格区间查询
			if(StringUtils.isNotBlank(vo.getPrice())){
				//7.5.1 将价格区间进行拆分
				String[] split = vo.getPrice().split("-");  // split[0]: 最小价格  split[1]: 最高价格 3000-*
				if(split[1].equals("*")){
					//7.5.2 得到最小值的query
					Query minPrirceQuery = RangeQuery.of(rq -> rq.field("price").gte(JsonData.of(split[0])))._toQuery();   // gte: greateThenEqual()
					//7.5.3 将最小query放到查询列表中
					filterQueryList.add(minPrirceQuery);
				}else{
					//7.5.4 价格查询范围
					Query priceQuery = RangeQuery.of(rq -> rq.field("price").gte(JsonData.of(split[0])).lte(JsonData.of(split[1])))._toQuery();
					//7.5.5 价格查询添加到查询列表中
					filterQueryList.add(priceQuery);
				}
			}
			//8. 排序查询
			if (StringUtils.isNotBlank(vo.getSort())) {
				String sort = vo.getSort();
				String[] split = sort.split(":");           // 第一部分：排序字段名 第二部分：排序的方式（升或降）
				if(split[1].equalsIgnoreCase("ASC")){
					//8.1 定义排序的操作对象
					SortOptions sortOptions = SortOptions.of(so -> so.field(sf -> sf.field(split[0]).order(SortOrder.Asc)));
					//8.2 将其放到排序的集合中
					sortList.add(sortOptions);
				}else{
					SortOptions sortOptions = SortOptions.of(so -> so.field(sf -> sf.field(split[0]).order(SortOrder.Desc)));
					sortList.add(sortOptions);
				}
			}
			//9. 创建查询请求对象
			// 9.1 当有查询关键字时
			request = SearchRequest.of(req -> req.index("item")
											//① 定义组合查询（内部是一个过滤查询，可以进行多字段匹配查询，分类查询、品牌、规格、价格区间查询）
											.query(q -> q.bool(qb -> qb.filter(filterQueryList)))
											//② 进行按照category进行聚合分组（当然，是在前面进行多字段查询后的分组）
											.aggregations("categoryAggs",cateAgg -> cateAgg.terms(ct -> ct.field("category")))
											//③ 高亮查询
											.highlight(h -> h.preTags("<span style='color:red'>").postTags("</span>").fields("title",hlf->hlf))
											//④ 排序查询
											.sort(sortList)
											//⑤ 分页查询
											.from((page-1)*pageSize)        // 代表从哪条记录开始
											.size(pageSize)                 // 代表每页显示条数
			);
		}else{
			//9.2 无搜索关键字时查询
			request = SearchRequest.of(req -> req.index("item")
												.query(q -> q.matchAll(ma -> ma))
												.from((page-1)*pageSize)        // 代表从哪条记录开始
												.size(pageSize)
			);
		}
		//10. 得到查询响应对象
		SearchResponse<Item> searchResponse = client.search(request, Item.class);
		//11. 得到响应的硬撞集合对象
		HitsMetadata<Item> hits = searchResponse.hits();
		//12. 得到总记录数
		long total = hits.total().value();
		//13. 计算总页数
		long totalPage = (long) Math.ceil(total / (pageSize * 1.0));
		List<Item> itemList = new ArrayList<>();
		//14. 遍历结果对象将商品信息添加到列表中
		for (Hit<Item> hit : hits.hits()) {
			//14.1 得到原数据
			Item source = hit.source();
			//14.2 得到高亮查询结果数据
			Map<String, List<String>> highlight = hit.highlight();   // map的key代表高亮查询字段
			List<String> titleList = highlight.get("title");
			if(!Collections.isEmpty(titleList)){
				//14.3 得到高亮查询的字段的值
				String s = titleList.get(0);
				//14.4 将得到的高亮数据覆盖原始的title数据
				source.setTitle(s);
			}
			//14.5 将处理过的原始数据添加到列表集合中
			itemList.add(source);
		}
		//15. 定义分类数据
		List<String> categoryList = new ArrayList<>();
		//16. 得到分组信息
		Map<String, Aggregate> aggregations = searchResponse.aggregations();
		//16.1 得到我们自己命名的分组对象
		Aggregate categoryAggs = aggregations.get("categoryAggs");
		if(categoryAggs != null){
			//16.2 得到分组聚合的数据
			Buckets<StringTermsBucket> buckets = categoryAggs.sterms().buckets();
			//16.3 遍历数组数据
			for (StringTermsBucket bucket : buckets.array()) {
				//16.4 得到数组数据
				String key = bucket.key();
				//16.5 将分类数据放到分类集合中
				categoryList.add(key);
			}
		}
		//17. 定义分类的名字
		String category = "";
		if(StringUtils.isNotBlank(vo.getCategory())){
			category = vo.getCategory();
		}else if(!Collections.isEmpty(categoryList)){
			category = categoryList.get(0);
		}

		//18. 根据分类查询出品牌及规格列表
		Map brandAndSpecMap = findBrandsAndSpecsByCategory(category);

		//19. 将列表添加到返回的集合中
		resultMap.put("itemList",itemList);
		resultMap.put("categoryList",categoryList);
		resultMap.putAll(brandAndSpecMap);
		resultMap.put("total",total);               // 总记录数
		resultMap.put("totalPage",totalPage);       // 总页数
		return resultMap;
	}
	//2. 从redis中得到品牌及规格列表
	private Map findBrandsAndSpecsByCategory(String category) {
		Map map = new HashMap();
		if(StringUtils.isNotBlank(category)){
			//2.1 得到模板id
			String typeId = redisTemplate.opsForHash().get("itemCatList", category) + "";
			//2.2 根据模板id找到品牌列表及规格列表
			String  brandListStr = redisTemplate.opsForHash().get("brandList", typeId) + "";
			List<Map> brandList = JSON.parseArray(brandListStr, Map.class);
			//2.3 将品牌列表放到map中
			map.put("brandList",brandList);
			//2.4 将规格列表放到map中
			String specListStr = redisTemplate.opsForHash().get("specList",typeId) + "";
			List<Map> specList = JSON.parseArray(specListStr, Map.class);
			//2.5 将规格列表放到map中
			map.put("specList",specList);
		}else{
			map.put("specList",new ArrayList<>());
			map.put("brandList",new ArrayList<>());
		}
		return map;
	}
}
